/*
 * Unidata Platform
 * Copyright (c) 2013-2020, UNIDATA LLC, All rights reserved.
 *
 * Commercial License
 * This version of Unidata Platform is licensed commercially and is the appropriate option for the vast majority of use cases.
 *
 * Please see the Unidata Licensing page at: https://unidata-platform.com/license/
 * For clarification or additional options, please contact: info@unidata-platform.com
 * -------
 * Disclaimer:
 * -------
 * THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND
 * REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE
 * IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY,
 * FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND
 * THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING.
 */
package org.unidata.mdm.rest.v1.meta.converter.entities;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.unidata.mdm.meta.dto.GetEntitiesGroupsDTO;
import org.unidata.mdm.meta.type.model.entities.EntitiesGroup;
import org.unidata.mdm.meta.type.model.entities.Entity;
import org.unidata.mdm.meta.type.model.entities.LookupEntity;
import org.unidata.mdm.meta.util.ModelUtils;
import org.unidata.mdm.rest.v1.meta.exception.MetaRestExceptionIds;
import org.unidata.mdm.rest.v1.meta.ro.NamedDisplayableRO;
import org.unidata.mdm.rest.v1.meta.ro.NamedDisplayableVisibilityRO;
import org.unidata.mdm.rest.v1.meta.ro.groups.FilledEntityGroupMappingRO;
import org.unidata.mdm.rest.v1.meta.ro.groups.FilledEntityGroupRO;
import org.unidata.mdm.rest.v1.meta.ro.groups.LightweightEntityGroupMappingRO;
import org.unidata.mdm.rest.v1.meta.ro.groups.LightweightEntityGroupRO;
import org.unidata.mdm.system.exception.PlatformBusinessException;

public class EntitiesGroupConverter {

    private static final Comparator<LightweightEntityGroupRO> groupComparator = Comparator.comparing(e -> e.getDisplayName().toLowerCase());

    private static final Comparator<NamedDisplayableVisibilityRO> definitionComparator = Comparator.comparing(e -> e.getDisplayName().toLowerCase());

    private EntitiesGroupConverter() { }

    public static List<FilledEntityGroupRO> extractFilledNodes(GetEntitiesGroupsDTO groupDefs) {
        List<FilledEntityGroupRO> groupNodes = new ArrayList<>();
        if (Objects.nonNull(groupDefs)) {

            for (Entry<String, EntitiesGroup> wrapper : groupDefs.getGroups().entrySet()) {

                FilledEntityGroupRO filledEntityGroupNode = new FilledEntityGroupRO();
                filledEntityGroupNode.setName(wrapper.getKey());
                filledEntityGroupNode.setDisplayName(wrapper.getValue().getDisplayName());

                List<Entity> registers = groupDefs.getNestedEntities(wrapper.getKey());
                Collection<NamedDisplayableVisibilityRO> entityDefinitions = registers.stream()
                    .map(en -> new NamedDisplayableVisibilityRO(en.getName(), en.getDisplayName(), en.getDescription(), en.isDashboardVisible()))
                    .sorted(definitionComparator)
                    .collect(Collectors.toList());

                List<LookupEntity> nestedLookups = groupDefs.getNestedLookupEntities(wrapper.getKey());
                Collection<NamedDisplayableVisibilityRO> lookupEntityDefinitions = nestedLookups.stream()
                    .map(en -> new NamedDisplayableVisibilityRO(en.getName(), en.getDisplayName(), en.getDescription(), en.isDashboardVisible()))
                    .sorted(definitionComparator)
                    .collect(Collectors.toList());

                filledEntityGroupNode.setEntities(entityDefinitions);
                filledEntityGroupNode.setLookupEntities(lookupEntityDefinitions);

                groupNodes.add(filledEntityGroupNode);
            }
            groupNodes.sort(groupComparator);
        }
        return groupNodes;
    }

    public static List<LightweightEntityGroupRO> extractFlatNodes(GetEntitiesGroupsDTO groupDefs) {
        return groupDefs.getGroups().entrySet()
            .stream()
            .map(g -> new LightweightEntityGroupRO(g.getValue().getDisplayName(), g.getKey()))
            .sorted(groupComparator)
            .collect(Collectors.toList());
    }

    @Deprecated
    public static FilledEntityGroupMappingRO toFilled(GetEntitiesGroupsDTO groupDefs) {

        List<FilledEntityGroupRO> groupNodes = new ArrayList<>();
        if (Objects.nonNull(groupDefs)) {

            for (Entry<String, EntitiesGroup> wrapper : groupDefs.getGroups().entrySet()) {

                FilledEntityGroupRO filledEntityGroupNode = new FilledEntityGroupRO();
                filledEntityGroupNode.setName(wrapper.getKey());
                filledEntityGroupNode.setDisplayName(wrapper.getValue().getDisplayName());

                List<Entity> registers = groupDefs.getNestedEntities(wrapper.getKey());
                Collection<NamedDisplayableVisibilityRO> entityDefinitions = registers.stream()
                        .map(en -> new NamedDisplayableVisibilityRO(en.getName(), en.getDisplayName(), en.getDescription(), en.isDashboardVisible()))
                        .sorted(definitionComparator)
                        .collect(Collectors.toList());

                List<LookupEntity> nestedLookups = groupDefs.getNestedLookupEntities(wrapper.getKey());
                Collection<NamedDisplayableVisibilityRO> lookupEntityDefinitions = nestedLookups.stream()
                        .map(en -> new NamedDisplayableVisibilityRO(en.getName(), en.getDisplayName(), en.getDescription(), en.isDashboardVisible()))
                        .sorted(definitionComparator)
                        .collect(Collectors.toList());

                filledEntityGroupNode.setEntities(entityDefinitions);
                filledEntityGroupNode.setLookupEntities(lookupEntityDefinitions);

                groupNodes.add(filledEntityGroupNode);
            }

            Collections.sort(groupNodes, groupComparator);
        }

        return new FilledEntityGroupMappingRO(groupNodes);
    }

    public static LightweightEntityGroupMappingRO toFlat(GetEntitiesGroupsDTO groupDefs) {

        Collection<LightweightEntityGroupRO> groupNodes = groupDefs.getGroups().entrySet().stream()
                .map(g -> new LightweightEntityGroupRO(g.getValue().getDisplayName(), g.getKey()))
                .sorted(groupComparator)
                .collect(Collectors.toList());

        return new LightweightEntityGroupMappingRO(groupNodes);
    }

    public static EntitiesGroup from(FilledEntityGroupMappingRO mapping) {

        Map<String, Collection<EntitiesGroup>> flatMap = new HashMap<>();
        Collection<FilledEntityGroupRO> groups = mapping.getGroupNodes();

        for (FilledEntityGroupRO group : groups) {

            String[] splitPath = ModelUtils.splitPath(group.getName());
            String groupName = splitPath[splitPath.length - 1];
            if (StringUtils.isBlank(groupName) || StringUtils.isBlank(group.getDisplayName())) {
                throw new PlatformBusinessException(
                        "Group doesn't contain name or title",
                        MetaRestExceptionIds.EX_META_DATA_GROUP_NAME_OR_TITLE_ABSENT
                );
            }

            EntitiesGroup groupDef = new EntitiesGroup()
                    .withName(groupName)
                    .withDisplayName(group.getDisplayName())
                    .withMappedLookups(group.getLookupEntities().stream()
                            .filter(Objects::nonNull)
                            .map(NamedDisplayableRO::getName)
                            .filter(Objects::nonNull)
                            .collect(Collectors.toList()))
                    .withMappedRegisters(group.getEntities().stream()
                            .filter(Objects::nonNull)
                            .map(NamedDisplayableRO::getName)
                            .filter(Objects::nonNull)
                            .collect(Collectors.toList()));

            String mapKey = splitPath.length == 1
                    ? StringUtils.EMPTY
                    : group.getName().substring(0, group.getName().length() - groupName.length() - 1);

            flatMap
                .computeIfAbsent(mapKey, k -> new ArrayList<>())
                .add(groupDef);
        }

        assemble(flatMap.get(StringUtils.EMPTY), flatMap, StringUtils.EMPTY);
        return flatMap.get(StringUtils.EMPTY).iterator().next();
    }

    private static void assemble(Collection<EntitiesGroup> groups, Map<String, Collection<EntitiesGroup>> flatMap, String parentPath) {
        for (EntitiesGroup group : groups) {
            Collection<EntitiesGroup> inner = flatMap.getOrDefault(ModelUtils.joinPath(parentPath, group.getName()), new ArrayList<>());
            group.withInnerGroups(inner);
            assemble(inner, flatMap, group.getName());
        }
    }
}
